using System;
using System.Collections.Generic;
using System.Linq;
using Urs.Core;
using Urs.Core.Caching;
using Urs.Core.Data;
using Urs.Data.Domain.Stores;

namespace Urs.Services.Stores
{
    public partial class CategoryService : ICategoryService
    {
        #region Constants
        private const string CATEGORIES_BY_SHOWONHOMEPAGE = "Urs.category.shoponhomepage";
        private const string FEATUREDCATEGORIES_BY_SHOWONHOMEPAGE = "Urs.category.featuredcategory.shoponhomepage";
        private const string CATEGORIES_BY_ID_KEY = "Urs.category.id-{0}";
        private const string CATEGORIES_BY_PARENT_CATEGORY_ID_KEY = "Urs.category.byparent-{0}-{1}";
        private const string PRODUCTCATEGORIES_ALLBYCATEGORYID_KEY = "Urs.goodscategory.allbycategoryid-{0}-{1}-{2}-{3}";
        private const string PRODUCTCATEGORIES_ALLBYPRODUCTID_KEY = "Urs.goodscategory.allbygoodsid-{0}-{1}-{2}";
        private const string PRODUCTCATEGORIES_BY_ID_KEY = "Urs.goodscategory.id-{0}";
        private const string CATEGORIES_PATTERN_KEY = "Urs.category.";
        private const string PRODUCTCATEGORIES_PATTERN_KEY = "Urs.goodscategory.";

        #endregion

        #region Fields

        private readonly IRepository<Category> _categoryRepository;
        private readonly IRepository<GoodsCategory> _goodsCategoryRepository;
        private readonly IRepository<Goods> _goodsRepository;
        private readonly IWorkContext _workContext;
        private readonly ICacheManager _cacheManager;

        #endregion

        #region Ctor

        public CategoryService(ICacheManager cacheManager,
            IRepository<Category> categoryRepository,
            IRepository<GoodsCategory> goodsCategoryRepository,
            IRepository<Goods> goodsRepository,
            IWorkContext workContext)
        {
            this._cacheManager = cacheManager;
            this._categoryRepository = categoryRepository;
            this._goodsCategoryRepository = goodsCategoryRepository;
            this._goodsRepository = goodsRepository;
            this._workContext = workContext;
        }

        #endregion

        #region Methods

        public virtual void DeleteCategory(Category category)
        {
            if (category == null)
                throw new ArgumentNullException("category");

            category.Deleted = true;
            UpdateCategory(category);
        }

        public virtual IPagedList<Category> GetAllCategories(string categoryName = "",
            int pageIndex = 0, int pageSize = int.MaxValue, bool showHidden = false)
        {
            var query = _categoryRepository.Table;
            if (!showHidden)
                query = query.Where(c => c.Published);
            if (!String.IsNullOrWhiteSpace(categoryName))
                query = query.Where(c => c.Name.Contains(categoryName));
            query = query.Where(c => !c.Deleted);
            query = query.OrderBy(c => c.ParentCategoryId).ThenBy(c => c.DisplayOrder);

            var unsortedCategories = query.ToList();

            var sortedCategories = unsortedCategories.SortCategoriesForTree();

            return new PagedList<Category>(sortedCategories, pageIndex, pageSize);
        }

        public IList<Category> GetAllCategoriesByParentCategoryId(int parentCategoryId, bool showHidden = false)
        {
            string key = string.Format(CATEGORIES_BY_PARENT_CATEGORY_ID_KEY, parentCategoryId, showHidden);
            return _cacheManager.Get(key, () =>
            {
                var query = _categoryRepository.Table;
                if (!showHidden)
                    query = query.Where(c => c.Published);
                query = query.Where(c => c.ParentCategoryId == parentCategoryId);
                query = query.Where(c => !c.Deleted);
                query = query.OrderBy(c => c.DisplayOrder);

                var categories = query.ToList();
                return categories;
            });

        }

        public virtual IList<Category> GetAllCategoriesDisplayedOnHomePage()
        {
            return _cacheManager.Get<IList<Category>>(CATEGORIES_BY_SHOWONHOMEPAGE, () =>
            {
                var query = from c in _categoryRepository.Table
                            orderby c.DisplayOrder
                            where c.Published &&
                            !c.Deleted &&
                            c.ShowOnHomePage
                            select c;

                return query.ToList();
            });
        }

        public virtual Category GetCategoryById(int categoryId)
        {
            if (categoryId == 0)
                return null;

            string key = string.Format(CATEGORIES_BY_ID_KEY, categoryId);
            return _cacheManager.Get(key, () =>
            {
                var category = _categoryRepository.GetById(categoryId);
                return category;
            });
        }

        public virtual void InsertCategory(Category category)
        {
            if (category == null)
                throw new ArgumentNullException("category");

            _categoryRepository.Insert(category);

            _cacheManager.RemoveByPattern(CATEGORIES_PATTERN_KEY);
            _cacheManager.RemoveByPattern(PRODUCTCATEGORIES_PATTERN_KEY);

        }

        public virtual void UpdateCategory(Category category)
        {
            if (category == null)
                throw new ArgumentNullException("category");

            var parentCategory = GetCategoryById(category.ParentCategoryId);
            while (parentCategory != null)
            {
                if (category.Id == parentCategory.Id)
                {
                    category.ParentCategoryId = 0;
                    break;
                }
                parentCategory = GetCategoryById(parentCategory.ParentCategoryId);
            }

            _categoryRepository.Update(category);

            _cacheManager.RemoveByPattern(CATEGORIES_PATTERN_KEY);
            _cacheManager.RemoveByPattern(PRODUCTCATEGORIES_PATTERN_KEY);
        }


        public virtual void DeleteGoodsCategory(GoodsCategory goodsCategory)
        {
            if (goodsCategory == null)
                throw new ArgumentNullException("goodsCategory");

            _goodsCategoryRepository.Delete(goodsCategory);

            _cacheManager.RemoveByPattern(CATEGORIES_PATTERN_KEY);
            _cacheManager.RemoveByPattern(PRODUCTCATEGORIES_PATTERN_KEY);
        }

        public virtual IPagedList<GoodsCategory> GetGoodsCategoriesByCategoryId(int categoryId, int pageIndex, int pageSize, bool showHidden = false)
        {
            if (categoryId == 0)
                return new PagedList<GoodsCategory>(new List<GoodsCategory>(), pageIndex, pageSize);

            string key = string.Format(PRODUCTCATEGORIES_ALLBYCATEGORYID_KEY, showHidden, categoryId, pageIndex, pageSize);
            return _cacheManager.Get(key, () =>
            {
                var query = from pc in _goodsCategoryRepository.Table
                            join p in _goodsRepository.Table on pc.GoodsId equals p.Id
                            where pc.CategoryId == categoryId &&
                                  !p.Deleted &&
                                  (showHidden || p.Published)
                            orderby pc.DisplayOrder
                            select pc;

                var goodsCategories = new PagedList<GoodsCategory>(query, pageIndex, pageSize);
                return goodsCategories;
            });
        }

        public IList<Goods> GetFeaturedGoodss(int categoryId, bool showHidden = false)
        {
            var query = from pc in _goodsCategoryRepository.Table
                        join p in _goodsRepository.Table on pc.GoodsId equals p.Id
                        where pc.CategoryId == categoryId &&
                              !p.Deleted &&
                              (showHidden || p.Published)
                        orderby pc.DisplayOrder
                        select p;

            return query.ToList();
        }
        public virtual GoodsCategory GetGoodsCategoryById(int goodsCategoryId)
        {
            if (goodsCategoryId == 0)
                return null;

            string key = string.Format(PRODUCTCATEGORIES_BY_ID_KEY, goodsCategoryId);
            return _cacheManager.Get(key, () =>
            {
                return _goodsCategoryRepository.GetById(goodsCategoryId);
            });
        }

        public virtual IList<GoodsCategory> GetGoodsCategoriesByGoodsId(int goodsId)
        {
            var query = from pc in  _goodsCategoryRepository.Table
                        where pc.GoodsId == goodsId
                        orderby pc.DisplayOrder
                        select pc;

            return query.ToList();
        }

        public virtual void InsertGoodsCategory(GoodsCategory goodsCategory)
        {
            if (goodsCategory == null)
                throw new ArgumentNullException("goodsCategory");

            _goodsCategoryRepository.Insert(goodsCategory);

            _cacheManager.RemoveByPattern(CATEGORIES_PATTERN_KEY);
            _cacheManager.RemoveByPattern(PRODUCTCATEGORIES_PATTERN_KEY);
        }

        public virtual void UpdateGoodsCategory(GoodsCategory goodsCategory)
        {
            if (goodsCategory == null)
                throw new ArgumentNullException("goodsCategory");

            _goodsCategoryRepository.Update(goodsCategory);

            _cacheManager.RemoveByPattern(CATEGORIES_PATTERN_KEY);
            _cacheManager.RemoveByPattern(PRODUCTCATEGORIES_PATTERN_KEY);
        }

        public virtual string GetFormattedBreadCrumb(Category category, IList<Category> allCategories = null,
            string separator = ">>")
        {
            var result = string.Empty;

            var breadcrumb = this.GetCategoryBreadCrumb(category, allCategories, true);
            for (var i = 0; i <= breadcrumb.Count - 1; i++)
            {
                var categoryName = breadcrumb[i].Name;
                result = string.IsNullOrEmpty(result) ? categoryName : $"{result} {separator} {categoryName}";
            }

            return result;
        }

        public IList<Category> GetCategoriesByGoodsId(int goodsId)
        {
            var query = from c in _categoryRepository.Table
                        join pc in _goodsCategoryRepository.Table on c.Id equals pc.CategoryId
                        where pc.GoodsId == goodsId
                        orderby pc.DisplayOrder
                        select c;

            return query.ToList();
        }

        public virtual IList<Category> GetCategoryBreadCrumb(Category category, IList<Category> allCategories = null, bool showHidden = false)
        {
            if (category == null)
                throw new ArgumentNullException(nameof(category));

            var result = new List<Category>();

            var alreadyProcessedCategoryIds = new List<int>();

            while (category != null && //not null
                !category.Deleted && //not deleted
                (showHidden || category.Published) && //published
                !alreadyProcessedCategoryIds.Contains(category.Id)) //prevent circular references
            {
                result.Add(category);

                alreadyProcessedCategoryIds.Add(category.Id);

                category = allCategories != null ? allCategories.FirstOrDefault(c => c.Id == category.ParentCategoryId)
                    : this.GetCategoryById(category.ParentCategoryId);
            }

            result.Reverse();
            return result;
        }
        #endregion
    }
}
